// -*-c#-*-
//
// License: New BSD License : http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2008, Brothers In Arms/Burl Nyswonger
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//    * Neither the name of Brothers In Arms/Burl Nyswonger nor the names
//      of its contributors may be used to endorse or promote products
//      derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// ----------------------------------------------------------------------------
//
// January 12, 2008
//   Becaue I host this project on Google-Code, I am required to use one
//   of the licenses they provide.  The New BSD License seemed best to me.
//
//  Visit the project site to collaborate on development:
//      http://code.google.com/p/ut3-sniper-game
//
//  Visit Brothers In Arms:
//      http://bia-2k4.com
//
//  + Burl
//    aka: [BIA]DarthMushak
// ----------------------------------------------------------------------------
//@L

class BiaVote extends Info
    config(BiaVote);

/* ------------------------------------------------------------------------- */

`include( BiaVote/Classes/BiaStdInc.uci );
`include( BiaVote/Classes/BiaVote.uci );

/* ------------------------------------------------------------------------- */

// enums...

enum EBiaVoteMessage
{
    EBiaVoteMessage_KeyMapped,
    EBiaVoteMessage_Voting,
    EBiaVoteMessage_VoteFailed,
    EBiaVoteMessage_VotePassed,
    EBiaVoteMessage_RandMap
};


enum biaVType
{
    vtNone, vtProperty, vtCat, vtVote, vtGame, vtMap, vtKick, vtAddBot
};

// mutually exclusive vote types are Game and Map only...
// vtAddBot and vtKick may conflict, but are not mutually exclusive
// in the sense of the above...
`define isMx(xV) ( (`xV) == vtMap || (`xv) == vtGame )


// Structs...

struct sVoteRules
{
    var biaVtype vType;
    var int nMultiWin;
    var bool bOnlyGameSwitch, bVoteable;
    var string CatName;
    structdefaultproperties
    {
        bVoteable=false
        vType=vtNone
        nMultiWin=1
        bOnlyGameSwitch=false
    }
};

struct sUIList
{
    var string Title;
    var biaVType vType;
    var int Id, nVotes;
    var bool bSynced;
    structdefaultproperties
    {
        Title=""
        vType=vtNone
        Id=-1
        nVotes=0
        bSynced=false
    }
};

struct sBiaVotes
{
    var biaVtype vType;
    var int nVotes, Id;
    var string Title;
    structdefaultproperties
    {
        vType=vtNone
        Title=""
        nVotes=0
        Id=-1
    }
};


struct sVoteable
{
    var string Title, Value;
    var biaVType VType;
    var int Id, Votes, cfgIndex;
    var array<string>gMask;
    structdefaultproperties
    {
        Title=""
        Value=""
        VType=vtNone
        Id=-1
        Votes=0
        gMask=()
        cfgIndex=-1
    }
};


struct sVoteInfo
{
    var float TimeStart, TimeEnd, LastVoteTime;
    var int nMaxVote, nVoting;
    var bool bVoting;
    var sBiaVotes Winner;
    var array<int> Voters;
    structdefaultproperties
    {
        TimeStart=0.0f
        TimeEnd=60.0f
        nMaxVote=0
        nVoting=0
        bVoting=false
    }
};

struct cfgGame
{
    var string Game, Title, GameClass, Options, RemoveItems;
    var bool bTeamGame, bEnabled;
    var int minPlayers, maxPlayers;
    structdefaultproperties
    {
        Game=""
        Title=""
        GameClass=""
        Options=""
        RemoveItems=""
        bTeamGame=false
        bEnabled=true
        minPlayers=1
        maxPlayers=64
    }
};

struct cfgMap
{
    var string Map, Title, Games;
    var bool bEnabled;
    var int minPlayers, maxPlayers;
//    var array<string>Games;
    structdefaultproperties
    {
        Title=""
        Map=""
        bEnabled=true
        minPlayers=1
        maxPlayers=64
        Games=""
    }
};

struct cfgItems
{
    var string Item, Group;
    structdefaultproperties
    {
        Item=""
        Group=""
    }
};


struct sPlayer
{
    var Controller PC;
    var BiaVoteClient BVC;
    var bool bIsBot;
    var string PlayerName;
`if(`isdefined(UNIT_TEST))
    var bool bUnitTest;
`endif
    var int Id;
    var int nVotes, nMxVotes, mxId;
    var biaVtype mxVtype;
    var array<int> Votes;
    
    structdefaultproperties
    {
        PC=None
        BVC=None
        Id=-1
        bIsBot=true
        nVotes=0
        mxId=0
        mxVType=vtNone
        nMxVotes=0
        Votes=(0,0,0,0,0)
    }
};


// ----------------------------------------------------------------------------

// To make this into a Mutator may be tricky... But I will try....
// singleton hack...
var BiaVote               MyInstance;
var float                 SpawnTime;
var bool                  bMutatorInterface, bGameMatchOver;
const s_err_no_instance = "unable to find instance of BiaVote";

// stats - don't want to save them in this class - perobject config seems busted, etc.
var BiaVoteStat           MyStats;


// configuration file....
var config int              BiaVoteCfgMajor, BiaVoteCfgMinor;
var config array<cfgMap>    Maps;
var config array<cfgGame>   Games;
var config array<cfgItems>  Items;
var config string           DefaultGame;
var config int              MaxVotes;
var config int              MaxMxVotes;
var config int              VoteTimeLimit;
var config string           FailSafeURL;
var config bool             EnableKickVoting, EnableAddBotVoting, MapsRemovePrefix;
var config int              EnableWelcome;

// config file version checking...
const CfgMajor=1;
const CfgMinor=1;


// Object...
var GameReplicationInfo   MyGRI;
var array<string>         aGames;    // a list of the game's
var string                sDefGame;
var int                   nMaxVotes;  // max number of votes allowed per-player, per-vote-cycle
var int                   nMaxMxVotes;
var int                   nVoteTimeLimit;
var bool                  bVoteReady, bStatsRecorded;
var sVoteInfo             biaVI;     // "Vote Info" - info to coordinate voting, etc.
var array<sVoteable>      biaVL;     // "Votable List" - everything "votable" that is static
var array<sUIList>        biaDL;     // "Dynamic Voteables" - voteable things that are "dynamic"
var bool                  bRestartGame; // flag that we are restarting...
var string                sFailSafeURL; // fall-back to this if confused about what map/game to load...
// defaults/config...
var int minVoteTimeLimit;
var int maxVoteTimeLimit;
var bool bMapVote, bMapVoteHandled;
var string sLastListGameCat;
var int nKickedBots, nExtraBots, nDesiredPlayers;

// this is used to track the "version" of the Dynamic-voteable-items list: biaDL[]
var int nDLListVer;
`define DynamicUIListModified() IncverDynamicUIList() 
//TODO: if more than 1 kind of Dynamic-votable is added to the system, the above won't work right...
//      Probably need to just merge all this crap into a single list anyway...


// used to track the "version" of the array: UILists[]
var int                   nListVer;
`define StaticUIListModified() nListVer += 1 

// used to track the "version" of the list: aBiaVotes[]
var int                   nVoteListVer;
`define VoteUIListModified() `BiaDbg("nVoteListVer:" $ nVoteListVer); nVoteListVer += 1; `BiaDbg("nVoteListVer: " $nVoteListVer) 

// uniqe Id's for voteable items come from here:
var int nBiaUId;
const nBiaUId_minimum = 0x100; // preserve lower-bits for special shit, perhaps?


//
var bool bOutcomeUnanimous, bOutcomeMajority, bEveryoneVoted;

//
// These are the lists we use to conduct votes...
var array<sUIList> UILists;
var string CurVGame;  // current game we are voting for (Game Acronym - from cfg...)
var string CurGameClass; 

// server-only, misc...
var string sNextMap, sCurrentMap, sLastVGame, sLastMap;
var array<sBiaVotes> aBiaVotes;     // to track what is voted on, etc.
var array<sPlayer> aPlayers; // players...
var array<sVoteRules> aVoteRules; // vote type rules...
var BiaVoteFauxVoteCollector MyVoteCollector;
var int nCanVote;

`if(`isdefined(UNIT_TEST))
var BiaVoteUnitTest bvUT;
`endif


// ----------------------------------------------------------------------------

function BiaVote_ServerInit( GameReplicationInfo GRI )
{
    local BiaVote B;
    local BiaVoteClient BC;

    `BiaEnter();

    switch(BiaVoteCfgMajor)
    {
        case CfgMajor:
            switch(BiaVoteCfgMinor)
            {
                case CfgMinor:
                    break;
                default:
                    `BiaWarn("NOTE: UTBiaVote.ini config-version-minor mismatch - you may be missing out on new features.  Check the documentation for BiaVote Version: " $ `BiaVoteVersion );
            }
            break;
        default:
            `BiaError("*** NOTICE: UTBiaVote.ini config-version-major mismatch - things may break, check the documentation for BiaVote Version: " $ `BiaVoteVersion );
    }

    // this is as good a time as any to spawn our stats...
    MyStats = Spawn(class'BiaVote.BiaVoteStat', self );

    `BiaLog("[BIA]Vote, Version " $ `BiaVoteVersion );
    
    if(ROLE == ROLE_Authority)
    {
   
        bStatsRecorded = false;
        bVoteReady = false;
        nDLListVer=0;
        nListVer=0;
        nVoteListVer=0;
        
        //Issue:4 - Can add too many bots - save the original value
        nDesiredPlayers = UTGame(WorldInfo.Game).DesiredPlayerCount;
        
        `BiaDbg("Initializing BiaVote Master - I am: " $ self );
        ForEach AllActors(class'BiaVote', B)
        {
            if( B != self )
            {
                `BiaError("another BiaVote Server object was found: " $ B);
            }
        }
        ForEach AllActors(class'BiaVoteClient', BC)
        {
            `BiaWarn("A BiaVoteClient object has been found: " $ BC );
        }

        MyGRI = GRI;
        
        nMaxMxVotes = max(2, MaxMxVotes);
        nMaxVotes = max(3, nMaxVotes);
        if( DefaultGame != "")
        {
            sDefGame = DefaultGame;
        }
        
        nVoteTimeLimit = min(max(VoteTimeLimit, max(VoteTimeLimit, minVoteTimeLimit)), maxVoteTimeLimit);
        
        biaVI.TimeEnd=nVoteTimeLimit;
                
        GotoState('ServerInit');
    }
    
    `BiaExit();
}

event Destroyed()
{
    local BiaVoteClient B;
    `BiaEnter();
    `BiaLog("Destroyed(" $ self $ ")");
    foreach AllActors( class'BiaVoteClient', B )
    {
        B.LogOut();
    }
    Super.Destroyed();
    `BiaExit();
}


// ----------------------------------------------------------------------------
//
// Execution States...
//


state ServerInit
{
    function ScanForNewBots() {}
    function BeginState(Name PrevState)
    {
        `BiaLog("state - ServerInit");
        sCurrentMap = WorldInfo.GetMapName();
        if( MyStats != None )
        {
            sLastVGame = MyStats.GetLastGame();
            sLastMap = MyStats.GetLastMap();
        }
        
        SetCurGameClass();
        
        SetupVote();
        SetupLists();
        NewVoteRound();

        `if(`isdefined(UNIT_TEST))
                DoUnitTests();
        `endif


        //Issue:6 - see below
        SpawnFauxVoteCollector();
        GotoState('VoteWait');        
    }
}


//Issue:6
// We always need our own VoteCollector - and it must be assigned to the
// UTGame's VoteCollector -- we do this *VERY EARLY* in case a restart
// happens for whatever reason (GetNextMap() in UTGame is called if the
// VoteCollector != None -- and if its our's, thats good...)
function UTVoteCollector SpawnFauxVoteCollector()
{
    if( MyVoteCollector == None )
    {
        `BiaDbg("Spawning Faux-VoteCollector class");
        UTGame(WorldInfo.Game).RestartWait = 130;
        UTGame(WorldInfo.Game).VoteDuration = 130;
        MyVoteCollector = Spawn(class'BiaVote.BiaVoteFauxVoteCollector', UTGame(WorldInfo.Game));
        UTGame(WorldInfo.Game).VoteCollector = MyVoteCollector;
    }
            
    return( MyVoteCollector );
}

state VoteWait
{
    function BeginState( Name PrevState )
    {
        `BiaLog("state - VoteWait");
        bVoteReady = True;
        SetTimer(5.0, true);

        // Nothing cares about new bots except kick voting, atm...        
        if( EnableKickVoting )
        {
            SetTimer(5.0, true, 'ScanForNewBots');
        }
    }
    event ContinuedState()
    {
        local bool bGameChange;
        if( biaVI.bVoting )
        {
            biaVI.bVoting = false;
            SetTimer(5.0,true);
            `BiaLog("state - VoteWait");
            bGameChange = EvaluateVoteRound();
            if( bMapVote && ! bGameChange )
            {
                GotoState('RestartGame');                
            }
            else
            {
                NewVoteRound( bGameChange );
            }
        }
    }
    singular function UpdateVotes(sVoteable v, int PlayerIndex)
    {
        PrepVoteRound();
        ReallyUpdateVotes(v, PlayerIndex);
        PushState('Voting');
    }
    
    event Timer()
    {
    }
}

state Voting
{
    function ScanForNewBots() {}
    event PushedState()
    {
        `BiaDbg("Voting has begun");
        SetTimer(1.0, true);
    }
    singular function UpdateVotes(sVoteable v, int PlayerIndex)
    {
        ReallyUpdateVotes(v, PlayerIndex);
    }
    function HandleMapVote()
    {
        biaVI.TimeStart = WorldInfo.TimeSeconds;
        biaVI.TimeEnd = biaVI.TimeStart + nVoteTimeLimit;
        bMapVoteHandled = true;
    }
    
    event Timer()
    {
        local bool vdone;
        vdone = false;
        if( ShouldTerminateVote() )
        {
            `BiaLog("vote time limit reached");
            vdone = true;
        }
        if(vdone)
        {
            PopState();
        }
    }
}

function HackPlayerScoreboards()
{
    local int i;
    
    for(i = 0; i < aPlayers.Length; i++)
    {
        if(IsCitizen(i))
        {
            aPlayers[i].BVC.MatchOver();
            break;
        }
    }
}

function RecordStats()
{
    local array<PlayerController> aPC;
    local PlayerController PC;

    if( MyStats != None )
    {
        aPC.Length = 0;
        foreach WorldInfo.AllControllers(class'PlayerController', PC)
        {
            aPC.AddItem(PC);
        }
        MyStats.RecordStats(sCurrentMap, CurVGame, aPC);
        bStatsRecorded = true;
    }
    else
    {
        `BiaError("Unable to record stats - no Stats object found");
    }
}

state RestartGame
{
    function ScanForNewBots() {}
    function bool EvaluateVoteRound() {}
    reliable server function SubmitVote( int Id, PlayerController PC, BiaVoteClient BVC ) {}
    singular function RestartPlayer( Controller Player, optional bool bBatchOp = false ) {}  
    //here
    function BeginState( name PrevState )
    {
        bRestartGame = true; 
        ClearTimer();
        ClearTimer('VoteFailed');
        RecordStats();
        SetTimer(5.0, false, 'DoRestart');
        SetTimer(0.5, false, 'CloseClients');
        UTGame(WorldInfo.Game).GotoState('');
    }
}

// ----------------------------------------------------------------------------
//
// Globals for state funcs...
//

event Timer() {}

singular function UpdateVotes(sVoteable v, int PlayerIndex)
{
    if( IsCitizen(PlayerIndex))
    {
        aPlayers[PlayerIndex].BVC.AckVote("Failed to register vote!");
    }
    `BiaWarn("called outside of state code");
}



// ----------------------------------------------------------------------------
//
// Vote Decision Logic...

//TODO: after clean-up/rewrite - make the vote-round termination logic "configurable" in
// the ini file -- should be able to set it so that the vote always goes to the end of
// the timer (whoever might want that) - or to stop whenever a max vote has accumulated,
// even though someone might change their mind -- but wait! there's more!  Should be
// able to configure things such that "voting idlers" are taken into account, etc.
// so a lot will need to go into the decision -- need to refactor everything first
// though - don't want to iterate over the damned aBiaVotes[] array 1000 times/second!
//
private function bool ShouldTerminateVote()
{
    local bool bLag;
    
    if( bOutcomeUnanimous || WorldInfo.TimeSeconds > biaVI.TimeEnd )
    {
        return true;
    }
    bLag = ((WorldInfo.TimeSeconds - biaVI.LastVoteTime) > 4) ? true : false;
    if( bOutcomeMajority && bLag )
    {
        return true;
    }
    if( !bMapVote )
    {
        if(bEveryoneVoted && bLag )
        {
            return true;
        }
    }
    return false;
}


//
// CountVotesFor()
//
// Input: vt == the vote type (vtGame, vtMap, etc.) we want to tally up...
// Input: bClear (Optional:true) : will clear out aIdx first, default is to append...
//
// Out  : aIdx (out) - will be set to an array of all votes in category vt,
//        each element in the array is an index into aBiaVotes() for the
//        vote info and they are sorted hightest->lowest (0...max)
//
// Return: int - the max number of votes for the category vt.
//
// Never pass-in bClear=false unless you know for certain that aIdx[] was just built
// by this same routine and that aBiaVotes[] has not been modified in the interim!
//
function int CountVotesFor( biaVtype vt, out array<int> aIdx, optional bool bClear = true )
{
    local int idx, vidx, nMax;
    local array<int> aCnt;
    local bool bInserted;
    
    `BiaEnter();
    
    aCnt.Length = 0;
    nMax = 0;
    if( bClear )
    {
        aIdx.Length = 0;
        `BiaDbg("Cleared aIdx[] - aIdx.Length == " $ aIdx.Length);
    }
    else
    {
        // ugh... need to rebuild aCnt[] and nMax
        for(idx = 0; idx < aIdx.Length; idx++)
        {
            aCnt.AddItem(aBiaVotes[aIdx[idx]].nVotes);
            nMax = max(nMax, aCnt[idx]);
        }
        
    }
    
    for(idx=0; idx < aBiaVotes.Length; idx++)
    {
        if(aBiaVotes[idx].vType == vt && aBiaVotes[idx].Id > 0 && aBiaVotes[idx].nVotes > 0)
        {
            nMax = max(nMax, aBiaVotes[idx].nVotes);
            bInserted = false;
            for(vidx = 0; vidx < aCnt.Length; vIdx++)
            {
                if( aCnt[vidx] < aBiaVotes[idx].nVotes )
                {
                    aCnt.InsertItem(vidx, aBiaVotes[idx].nVotes);
                    aIdx.InsertItem(vidx, idx);
                    `BiaDbg("Inserting aIdx[" $ vidx $ "] == " $ aIdx[vIdx] $ " (" $ idx $ ")");
                    bInserted = true;
                    break;
                }
            }
            if(!bInserted)
            {
                vidx = aCnt.Length;
                aCnt.Add(1);
                aIdx.Add(1);
                aCnt[vidx] = aBiaVotes[idx].nVotes;
                aIdx[vidx] = idx;
                `BiaDbg("Appending to aIdx[" $ vidx $ "] == " $ aIdx[vIdx] $ " (" $ idx $ ")");
            }
        }
    }
    `BiaAssert( aIdx.Length == aCnt.Length );
    `BiaExit();
    return( nMax );
}


//
// Input: nSelect - the number of selections to make... Can only 1 win, can 3 win, etc... ?
// Input: nMajority - The number of votes required to be a winning candidate (and be selected)
// Input: Votes - An array constructed by CountVotesFor() - NOTE: aBiaVotes[] must not be modified since then!
// Out:   aWinners - an array of indexes into aBiaVotes for the vote's associated with the selections made
// Return: the number of selections made.
//
function int DecideVote( int nSelect, int nMajority, array <int>Votes, out array<int> aWinners)
{
    local int tieIdx, selIdx, nSelected;

    `BiaEnter();
    `BiaDbg("Choosing up to " $ nSelect $ " winners >= " $ nMajority $" votes from an array of " $ Votes.Length $ " candidates");
    nSelected = 0;
    while( nSelect > 0 )
    {
        if(Votes.Length < 1)
        {
            `BiaDbg("No more votes to choose from");
            break;
        }
        selIdx = 0;
        `BiaDbg("candidate vote has " $ aBiaVotes[Votes[0]].nVotes $ " votes");
        if(aBiaVotes[Votes[selIdx]].nVotes < nMajority )
        {
            `BiaDbg("No more votes >= " $ nMajority);
            break;
        }
        selIdx = 0;
        for(tieIdx = 1; tieIdx < Votes.Length; tieIdx++)
        {
            if( aBiaVotes[Votes[tieIdx]].nVotes != aBiaVotes[Votes[selIdx]].nVotes )
            {
                break;
            }
        }
        if( tieIdx > 1 )
        {
            `BiaDbg("Choosing a random winner from " $ tieIdx $ " tied votes");
            selIdx = Rand( tieIdx );
        }
        else
        {
            `BiaDbg("Exactly 1 Winner exists");
        }
        aWinners.AddItem( Votes[selIdx] );
        `BiaDbg("Selected Votes[" $ selIdx $ "] == " $ Votes[selIdx]);
        Votes.Remove(selIdx, 1);
        nSelected += 1;
        
        nSelect = nSelect - 1;
    }
    `BiaDbg("A total of " $ nSelected $ " vote(s) were selected");
    `BiaExit();
    return(nSelected);
}

function bool EvaluateVoteRound()
{
    local int idx, nMajority, nMax, nMaxMx, nDecides, startIdx, nCitizens;
    local array<int> aWin, aWinners;
    local bool bTerminalVote;

    aWinners.Length = 0;
    aWin.Length = 0;
    bTerminalVote = false;

    nCitizens = CountCitizens();   
    nMajority = max(nCitizens, 1);
    
    if(nMajority > 1)
    {
        nMajority = (nMajority/2) + 1;
    }
    
    if( bMapVote )
    {
        nDecides = 1;
        bTerminalVote = true;
    }
    else
    {
        nDecides = nMajority;
    }
    
    `BiaDbg("Majority vote requires " $ nMajority $ " vote(s)");

    // build list of maps voted for...
    nMaxMx = CountVotesFor( vtMap, aWin );
    nMax = nMaxMx;
    
    // add list of games voted for to the list...
    nMaxMx = CountVotesFor( vtGame, aWin, false );
    nMax = nMaxMx;
    
    if( nMax >= nDecides )
    {
        `BiaDbg("A map or game type has won");
        if( DecideVote(1, nDecides, aWin, aWinners) > 0 )
        {
            `BiaDbg("Winning Mx Vote is: " $ aBiaVotes[aWinners[0]].Title);
            if( aBiaVotes[aWinners[0]].vType == vtMap )
            {
                `BiaDbg("Vote is terminal");
                bTerminalVote = true;
            }
            else
            {
                `BiaDbg("Game type has won - need to restart vote round...");
                vtGame_Won( aWinners[0] );
                return( true );
            }
        }
        else
        {
            `BiaDbg("Nothing has won the Mx Vote");
        }
    }

    for(idx = 0; idx < aVoteRules.Length; idx++)
    {
        nMax = CountVotesFor( aVoteRules[idx].vType, aWin );
        if( nMax >= nMajority )
        {
            `BiaDbg("A majority vote exists for " $ aVoteRules[idx].vType);
            startIdx = aWinners.Length;
            if( DecideVote( aVoteRules[idx].nMultiWin, nMajority, aWin, aWinners ) > 0 )
            {
                `BiaLog("number of " $ aVoteRules[idx].vType $ " winners is: " $ aWinners.Length - startIdx);
            }
        }
    }

    ShowVoteStats(nMajority, nDecides, biaVI.nVoting, nMaxMx, aWinners);


`if(`isdefined(UNIT_TEST))
return false;
`endif



    // ok... now do stuff with the winning votes...    
    for(idx = aWinners.Length - 1; idx > -1 ; idx--)
    {
        HandleWinningVote( bTerminalVote, aBiaVotes[aWinners[idx]] );
    }
    
    return false;
}


private function ShowVoteStats(int nM, int nD, int nV, int nMaxMx, array<int> aWin)
{
    local int i;
    
    `BiaLog("Vote Round Statistics:");
    `BiaLog("Players who can vote.......... : " $ nCanVote);
    `BiaLog("Players who voted............. : " $ nV);
    `BiaLog("End-Game Vote................. : " $ bMapVote );
    if(bMapVote)
    {
    `BiaLog("Min Votes Needed To Win a Map. : " $ nD);
    }
    else
    {
    `BiaLog("Majority requires............. : " $ nM);
    }
    if( bMapVote )
    {
    `BiaLog("EndGame Vote -- a Winner will be selected, no matter what");
    }
    `BiaLog("Max Votes for any Map/Game.....: " $ nMaxMx);
    for(i = aWin.Length - 1; i > -1; i--)
    {
    `BiaLog("A Winning Vote.................: " $ aBiaVotes[aWin[i]].Title $ " / " $
            aBiaVotes[aWin[i]].nVotes);
    }
}


function ReallyUpdateVotes(sVoteable v, int VoterIdx)
{
    local int voteIdx, tmpIdx, unvoteId, nPlayers, nMax;
    local string ackmsg, VoteType, VoteTitle;
    `BiaEnter();

    unvoteId = 0;
    
    if( ! IsCitizen(VoterIdx) )
    {
        `BiaError("voter not found in aPlayers[]");
        return;
    }

    VoteType = "voted for";
    VoteTitle = v.Title;
    
    if( `isMx(v.vType) )
    {
        if( aPlayers[voterIdx].nMxVotes > nMaxMxVotes )
        {
            PlayerAckVote(VoterIdx, "You can't vote for more Maps/Games this round");
            return;
        }
        if( aPlayers[voterIdx].mxId > 0 )
        {
            unvoteId = aPlayers[voterIdx].mxId;
            VoteType = "changed his vote to";
        }
        aPlayers[voterIdx].mxId = v.Id;
        aPlayers[voterIdx].mxvType = v.vType;
        aPlayers[voterIdx].nMxVotes += 1;
        aPlayers[voterIdx].nVotes += 1;
    }
    else
    {
        if( aPlayers[voterIdx].nVotes > nMaxVotes )
        {
            PlayerAckVote(VoterIdx, "You have used all your votes this round");
            return;
        }
        tmpIdx = aPlayers[voterIdx].Votes.Find(v.Id);

        if( tmpIdx > -1 )
        {
            unvoteId = v.Id;
            aPlayers[voterIdx].nVotes += 1;
            ackmsg = "Previous vote for " $ v.Title $ " removed.";
            VoteType = "revoked his vote for";
        }
        else
        {
            tmpIdx = aPlayers[voterIdx].Votes.Find( 0 );
            if( tmpIdx < 0 )
            {
                tmpIdx = aPlayers[voterIdx].Votes.Length;
                aPlayers[voterIdx].Votes.Add(1);
            }
            aPlayers[voterIdx].Votes[tmpIdx] = v.Id;
            aPlayers[voterIdx].nVotes += 1;
        }
    }

    if( unvoteId > 0 )
    {
        RevokeVote( unvoteId );
    }

    tmpIdx = biaVI.Voters.Find( aPlayers[voterIdx].Id );
    if( tmpIdx < 0 )
    {
        // Record that this guy voted.
        biaVI.Voters.AddItem( aPlayers[voterIdx].Id );
        biaVI.nVoting += 1;
    }

    voteIdx = aBiaVotes.Find('Id', v.Id);
    if( voteIdx < 0 )
    {
        voteIdx = aBiaVotes.Find('Id', 0);
        if( voteIdx < 0)
        {
            voteIdx = aBiaVotes.Length;
            aBiaVotes.Add(1);
        }
        aBiaVotes[voteIdx].vType = v.vType;
        aBiaVotes[voteIdx].Id = v.Id;
        aBiaVotes[voteIdx].nVotes = 0;
        aBiaVotes[voteIdx].Title = v.Title;
    }
    
    biaVI.LastVoteTime = WorldInfo.TimeSeconds;
    
    aBiaVotes[voteIdx].nVotes += 1;
    `BiaDbg("Vote Count is " $ aBiaVotes[voteIdx].nVotes $ " for: " $ v.Title );
    
    if(ackmsg == "")
    {
        ackmsg = "Vote Recorded!";
    }

    // flag that we have changed the votes...
    `VoteUIListModified();

    PlayerAckVote(voterIdx, ackmsg);
    
    BroadCastMessage( aPlayers[voterIdx].PlayerName $" "$ VoteType $": "$ VoteTitle);

    `BiaDbg("nVoteListVer:" $nVoteListVer);    
    UpdateClientVotes();
    `BiaDbg("nVoteListVer:" $nVoteListVer);    
    

// -------------------------------------------------------------------

    nPlayers = CountCitizens();
    
    if( nPlayers == 1 && v.Id != unvoteId )
    {
        bEveryoneVoted = true;
        bOutcomeUnanimous = true;
        return;
    }
    
    if( biaVI.nVoting >= nPlayers )
    {
        bEveryoneVoted = true;
    }
    
    // ugh... TODO: make a class to handle/abstract all this vote shit
    nMax = 0;
    for(tmpIdx = 0; tmpIdx < aBiaVotes.Length; tmpIdx++)
    {
        if( `IsMx( aBiaVotes[tmpIdx].vType ) )
        {
            nMax = max(nMax, aBiaVotes[tmpIdx].nVotes);
        }
    }
    if( nMax > (nPlayers/2) )
    {
        bOutcomeMajority = true;
        return;
    }

    
    `BiaExit();
}


function NewVoteRound(optional bool bGameChange = false)
{
    local int idx;
    
    for(idx = 0; idx < aPlayers.Length; idx++)
    {
        if( IsCitizen(idx) )
        {
            ResetPlayerVoteInfo(idx, bGameChange);
            if( aPlayers[idx].BVC != None )
            {
                aPlayers[idx].BVC.NewVoteRound(bGameChange);
            }
        }
    }
}



// ----------------------------------------------------------------------------
//
// Vote Actions...


function VoteFailed()
{
    `BiaLog("Vote Failed");
    if(bMapVote)
    {
        sNextMap = "";
        GotoState('RestartGame');
    }
}

function vtKick_Won( sBiaVotes v )
{
    local int idx;
    
    `BiaEnter();

    idx = aPlayers.Find('Id', v.Id);
    if( idx > - 1 && aPlayers[idx].PC != None )
    {
        BroadcastMessage("BiaVote, Kicking: " $ aPlayers[idx].PC.PlayerReplicationInfo.PlayerName);
        MyKick( aPlayers[idx].PC );
    }
    
    `BiaExit();
}

function vtAddBot_Won( sBiaVotes v )
{
    local int idx, nAdd, nUnkick;
    `BiaEnter();
    idx = biaVL.Find('Id', v.Id);
    if( idx > 0 )
    {
        nAdd = biaVL[idx].cfgIndex; // damn! no unions...
        `BiaDbg("Found addBot entry - numbots is: " $ nAdd);
        nUnkick = max(0, nKickedBots - nAdd );
        `BiaDbg("nUnkick: " $ nUnkick );
        if( nUnkick > 0 )
        {
            `BiaDbg("nKickedBots: " $ nKickedBots);
            nKickedBots = max( 0, nKickedBots - nUnKick );
            `BiaDbg("nKickedBots is now: " $ nKickedBots $ ", nAdd is: " $ nAdd - nUnKick);
            nAdd = nAdd - nUnKick;
        }
        nExtraBots = max(0, min( nAdd, 4 ));
        
        //Issue:4 - can add too many bots - cap at nDesiredPlayers
        UTGame(WorldInfo.Game).DesiredPlayerCount = min(UTGame(WorldInfo.Game).DesiredPlayerCount + nExtraBots, nDesiredPlayers);
        
        `BiaDbg("nExtraBots is: " $ nExtraBots);
    }
    else
    {
        `BiaDbg("Can't find entry for id " $ v.Id $ " in biaVL[]");
    }
    `BiaExit();
}

//
// URL must be like:  blahblah?blah?Mutator=Foo,bar,baz?blah?blah
//             not:   blahblah?blah?Mutator=Foo?Mutator=bar?blahblah...
//
function bool MyParseOpts( out string inopts, out string mutopts )
{
    local array<string> parts;
    local string opts;
    local int i;
    local bool gotmuts;
    
    gotmuts = false;
    opts = inopts;
    mutopts = "";
    inopts = "";
    
    parts.Length = 0;
    ParseStringIntoArray(opts, parts, "?", true);
    
    for(i = 0; i < parts.Length; i++) 
    {
        //TODO: question: are quoted strings allowed - if so, check for that here...
        if( Instr(parts[i], " " ) > -1 )
        {
            `BiaError("This option has a space in it: '" $ parts[i] $ "' -- skipping");
            continue;
        }
        if( `iInstr(parts[i], "Mutator=") == 0)
        {
            gotmuts = true;
            mutopts = (mutopts == "" ? "" : mutopts $ ",") $ Mid(parts[i], 8);
        }
        else
        {
            inopts = (inopts == "" ? "" : inopts $ "?" ) $ parts[i];
        }
    }
    
    return(gotmuts);
}

function vtMap_Won( sBiaVotes v )
{
    local int idx;
    local string opts, sMuts;
    
    `BiaEnter();
    
    idx = biaVL.Find('Id', v.Id);
    if( idx > 0)
    {
        sNextMap = biaVL[idx].Value;
        `BiaLog("A Map Type has won the vote: " $ sNextMap);
        
        opts = CurVGameToGameClass();
        if( opts != "" )
        {
            //TODO: remove leading/trailing/embedded-after '?' chars. (should be NONE!)
            sNextMap = sNextMap $ "?game=" $ opts;
        }

        //TODO: look at this on restart - probabl better than relying on
        // the stuff in stats.  Also, the mutator can use this to
        // remove actors per-game type, etc...
        if( CurVGame != "" )
        {
            sNextMap = sNextMap $ "?BiaVoteGame=" $ CurVGame;
        }
        
        // Hmm... I think this will be the only method soon - lol...
        if( bMutatorInterface )
        {
            sNextMap = sNextMap $ "?Mutator=BiaVote.BiaVoteMutator";
        }
        
        opts = CurVGameToOpts();
        if( opts != "" )
        {
            if(MyParseOpts(opts, sMuts))
            {
                sNextMap $= "," $ sMuts;
            }
            if(opts != "")
            {
                sNextMap = sNextMap $ "?" $ opts;
            }
        }
        `BiaLog("Switching To: " $ sNextMap);
    }
    GotoState('RestartGame');
    
    `BiaExit();
}

function HandleWinningVote( bool bTerminalVote, sBiaVotes V )
{
    switch( v.vType )
    {
        case vtKick:
            vtKick_Won( v );
            break;
        case vtAddBot:
            vtAddBot_Won( v );
            break;
        case vtMap:
            vtMap_Won( v );
            break;
        default:
            `BiaError("Unable to handle vote type " $ v.vType $ ", Title = " $ v.Title );
    }
}


//
// special case...
//
// Called only if restarting because of a Game Type winning a vote.
// winningIndex is the index into aBiaVotes[] for the winning vote...
//
function vtGame_Won( int winningIndex )
{
    local int idx;
    
    idx = biaVL.Find('Id', aBiaVotes[winningIndex].Id );
    if( idx > -1 )
    {
        CurVGame = biaVL[idx].gMask[0];
        SetupLists(true);
        PrepVoteRound(true);
    }
    else
    {
        // failed!
        SetupLists();
        PrepVoteRound();
    }
    PushState('Voting');
    return;
}

// ----------------------------------------------------------------------------
//
// Misc vote related utility functions...
//

private function IncVerDynamicUIList()
{
    nDLListVer = nDLListVer + ( EnableKickVoting ? 1 : 0 );
    nCanVote = CountCitizens();
}

function RevokeVote( int Id )
{
    local int voteIdx;
    
    if( Id < 1 )
    {
        return;
    }
    voteIdx = aBiaVotes.Find('Id', Id);
    if( voteIdx > -1 )
    {
        `BiaDbg("revoking vote for id " $ Id );
        aBiaVotes[voteIdx].nVotes = max(0, aBiaVotes[voteIdx].nVotes - 1);
    }
}

function PrepVoteRound( optional bool bGameChange = false )
{
    local int idx;
    
    biaVI.bVoting = false;
    //
    bOutcomeUnanimous = false;
    bOutcomeMajority  = false;
    bEveryoneVoted    = false;
    //
    biaVI.TimeStart = WorldInfo.TimeSeconds;
    biaVI.TimeEnd = biaVI.TimeStart + nVoteTimeLimit;
    biaVI.nVoting = 0;
    biaVI.nMaxVote = 0;
    biaVI.Winner.Id = -1;
    biaVI.Voters.Length = 0;
    
    // Hmm...
    if( bGameChange )
    {
        for(idx = 0; idx < aBiaVotes.Length; idx++)
        {
            if( `IsMx( aBiaVotes[idx].vType ) )
            {
                aBiaVotes[idx].vType = vtNone;
                aBiaVotes[idx].nVotes = 0;
                aBiaVotes[idx].Id = 0;
                aBiaVotes[idx].Title = "";
            }
        }
    }
    else
    {
        aBiaVotes.Length = 0;
    }
    
    `BiaDbg("Vote Round Ends in : " $ nVoteTimeLimit $ " seconds...");

    // and finally...
    biaVI.bVoting = true;
    
    // no need to notify if we are at end-game voting state...
    if( !bMapVote )
    {
//        BroadCastMessage("Vote in progress - Press the '"$`BiaVoteKeyString$"' key to participate");
//        BiaVoteMessageCast(EBiaVoteMessage_Voting);
          WorldInfo.Game.BroadcastLocalized(self, class'BiaVoteMessage', EBiaVoteMessage_Voting);
    }
    
    `VoteUIListModified();
}


function ResetPlayerVoteInfo( int Idx, optional bool bGameChange)
{
    local int i;
    
    if(Idx > -1 )
    {
        if( aPlayers[Idx].PC == None || !aPlayers[Idx].PC.IsA('PlayerController') )
        {
            return;
        }
        
        if( Idx < aPlayers.Length )
        {
            for(i=0; i< aPlayers[Idx].Votes.Length; i++)
            {
                aPlayers[Idx].Votes[i] = 0;
            }
            
            aPlayers[Idx].mxVType=vtNone;
            aPlayers[Idx].mxId=0;
            
            if( bGameChange )
            {
                aPlayers[idx].nVotes = max(0, aPlayers[Idx].nVotes - aPlayers[Idx].nMxVotes);
                aPlayers[Idx].nMxVotes = 0;
            }
            else
            {
                aPlayers[Idx].nMxVotes=0;
                aPlayers[Idx].nVotes=0;
            }
        }
    }
}


function RevokeVotes( Controller Player )
{
    local UTPlayerController PC;
    local int idx, vId;
    
    `BiaEnter();

    idx = aPlayers.Find('PC', Player);
    if( idx < 0 )
    {
        return;
    }
    PC = UTPlayerController( aPlayers[idx].PC );
    if( PC == None )
    {
        `BiaExit();
        return;
    }
    if( aPlayers[idx].mxId > 0 )
    {
        RevokeVote(aPlayers[idx].mxId);
    }
    foreach aPlayers[idx].Votes( vId )
    {
        RevokeVote(vId);
    }
    UpdateClientVotes();
    `BiaExit();
}



// ----------------------------------------------------------------------------
//
// Game Class interface... (well... its an "interface" anyway - lol )
//

function CleanUpEverything()
{    
    //TODO: Traverse all BVC's and clean up all references to everything...
    //      May need to deal with client side too - but I think its ok to
    //      leave it alone...
 
 
`if(`isdefined(DEBUG))
    class'BiaVote.BiaVote'.static.ReportWorldObjects(WorldInfo);
`endif
    
}

function DoRestart()
{
    bRestartGame = true;
    CleanUpEverything();
    `BiaDbg("Restarting Game now ... ");
    UTGame(WorldInfo.Game).RestartWait = 0;
    WorldInfo.Game.RestartGame();  // Not UTGame() !!
}

//
// Called from Game Class to decide if adding bots is needed...
// ugh.. mutator makes this tougher...
function bool NeedPlayers(int numP, int numB, int DesiredP)
{
    if( numP < 1 )
    {
        //TODO:
        //  everyone left the server (yeah - so its not that fun for some!) -
        //  kick-off a timer that will re-check this fact after a while, and
        //  if nobody is connecting/downloading/etc. then restart with all
        //  the defaults and a random map...
        //
        // (remember! this is called when the game is still starting-up, etc...)
        //
        // For now, just unkick any bots...
        nKickedBots = 0;
    }
    if( nExtraBots > 0 )
    {
        DesiredP = min(30, DesiredP + max(0, nExtraBots));
    }
    if( numP + numB + nKickedBots < DesiredP )
    {
        `BiaDbg("true: numP:"$numP$", numB:"$numB$", nKicked:"$nKickedBots$", DesiredP:"$DesiredP$", nExtraBots:"$nExtraBots);
        return true;
    }
    if( numP + numB < 2 )
    {
        `BiaDbg("true: numP:"$numP$", numB:"$numB$", nKicked:"$nKickedBots$", DesiredP:"$DesiredP$", nExtraBots:"$nExtraBots);
        return true;
    }
    `BiaDbg("false: numP:"$numP$", numB:"$numB$", nKicked:"$nKickedBots$", DesiredP:"$DesiredP$", nExtraBots:"$nExtraBots);
    return false;
}


function ServerLogin( Controller Player )
{
    local PlayerController PC;
    `BiaDbg("Player ReLogIn: " $ Player);
    
    PC = PlayerController( Player );
    if( PC != None )
    {
        RestartPlayer( PC );
    }
}

// ugh... this was all easier when it was integrated into the gametype! - lol
function ScanForNewBots()
{
    local int Idx, cIdx;
    local AIController BotC;
    local array<bool> aIsNew;
    local array<AIController> aFound;
    local bool bListChanged, bDelBot;

    if( bMapVote || bRestartGame )
    {
        return;
    }

    bListChanged = false;
    aFound.Length = 0;
    aIsNew.Length = 0;
    foreach WorldInfo.AllControllers(class'AIController', BotC)
    {
        Idx = aPlayers.Find('PC', BotC);
        if( Idx < 0 )
        {
            aFound.AddItem( BotC );
            aIsNew.AddItem( true );
        }
        else
        {
            aFound.AddItem( BotC );
            aIsNew.AddItem( false );
        }
    }
    for( Idx = 0; Idx < aPlayers.Length; Idx ++ )
    {
        if( aPlayers[Idx].PC != None && aPlayers[Idx].PC.IsA('AIController') )
        {
            bDelBot = true;
            for( cIdx = 0; cIdx < aFound.Length; cIdx += 1 )
            {
                if( aFound[cIdx] == aPlayers[Idx].PC )
                {
                    bDelBot = false;
                    break;
                }
            }
            if( bDelBot )
            {
                PlayerLogout( aPlayers[Idx].PC, true );
                bListChanged = true;
            }
        }
    }
    for( Idx = 0; Idx < aFound.Length; Idx++ )
    {
        if( aIsNew[Idx] )
        {
            RestartPlayer( aFound[Idx], true );
            bListChanged = true;
        }
    }
    
    if( bListChanged )
    {
        `DynamicUIListModified();
    }
}

`if(`isdefined(UNIT_TEST))
function ut_StartPlayer(string playername)
{
    local sPlayer P;
    local int Idx;
    
    P.bUnitTest = true;
    P.bIsBot = false;
    P.PlayerName = playername;
    P.Id = BiaNewUId();

    Idx = aPlayers.Length;    
    aPlayers.AddItem(P);
    
    `BiaLog("new player started: " $ P.PlayerName $ ", Id: " $ P.Id);
    // Adding new...
    ResetPlayerVoteInfo(Idx);
    biaAddDL( vtKick, aPlayers[Idx].Id, P.PlayerName, false);
    `BiaExit();    
}
`endif

singular function RestartPlayer( Controller Player, optional bool bBatchOp = false )
{
    local int Idx;
    
    `BiaEnter();
    
    Idx = aPlayers.Find('PC', Player);
    if( Idx < 0 )
    {
        Idx = aPlayers.Find('PC', None);
        if( Idx < 0 )
        {
            Idx = aPlayers.Length;
            aPlayers.Add(1);
        }
    }
    if( aPlayers[Idx].PC == None )
    {        
        // Adding new...
        aPlayers[Idx].PC = Player;
        aPlayers[Idx].Id = BiaNewUId();
        if( Player.IsA('UTPlayerController') )
        {
            ResetPlayerVoteInfo(Idx);
            aPlayers[Idx].bIsBot = false;
            if( aPlayers[Idx].BVC == None )
            {
                `BiaLog("Spawning BiaVoteClient for: " $ aPlayers[idx].PlayerName);
                aPlayers[Idx].BVC = Spawn( class'BiaVoteClient', PlayerController(Player));
            }
        }
        else
        {
            aPlayers[Idx].bIsBot = true;
            aPlayers[Idx].BVC = None;
        }

        aPlayers[idx].PlayerName = aPlayers[idx].PC.PlayerReplicationInfo.PlayerName;
        `BiaLog("new (player|bot) started: " $ aPlayers[idx].PlayerName);
        
        // Add Dynamic for Kick-voting...
        biaAddDL( vtKick, aPlayers[idx].Id, aPlayers[idx].PlayerName, bBatchOp);
    }    
    `BiaExit();
}


function PlayerLogout( Controller Player, optional bool bBatchOp = false )
{
    local int idx;
    
    `BiaEnter();

    RevokeVotes(Player);
    
    idx = aPlayers.Find('PC', Player);
    if( idx > -1 )
    {
        ResetPlayerVoteInfo(idx);
        // Remove from biaDL
        biaDelDL( aPlayers[idx].Id, bBatchOp );

//TODO: - this is messy - need to trash-collect on this array - simple faux-mark/sweep ???
        aPlayers[idx].Id = 0;
        aPlayers[idx].PC = None;
        aPlayers[idx].bIsBot = true;
        if( aPlayers[idx].BVC != None )
        {
            aPlayers[idx].BVC.Logout();
        }
        aPlayers[idx].BVC = None;
    }
    
    `BiaExit();
}

function BecameSpectator( PlayerController PC )
{
    `BiaEnter();
    RevokeVotes(PC);
    `BiaExit();
}

private function PrepForMapVote()
{
    if( !bGameMatchOver )
    {
        bGameMatchOver = true;
        SetTimer(8,false,'HackPlayerScoreboards');
        SetTimer(15,false,'DoMapVote');
        SetTimer(120,false,'VoteFailed');
    }
}

private function DoMapVote()
{
    local int i;
    
    `BiaEnter();
    
    if( !bMapVote )
    {
        `BiaDbg("MapVote mode entered.");
        bMapVote = true;
        for(i = 0; i < aPlayers.Length; i++)
        {
            if( IsCitizen(i) )
            {
                if( aPlayers[i].BVC != None )
                {
                    `BiaDbg("Invoking MapVote for: " $ aPlayers[i].PlayerName);
                    (aPlayers[i].BVC).DoMapVote();
                }
                else
                {
                    `BiaDbg("Player " $ aPlayers[i].PC $ " has no BiaVoteClient - can't vote!");
                }
            }
            else
            {
                `BiaDbg("Player " $ aPlayers[i].PC $ " is not a citizen - can't vote!");
            }
        }
        HandleMapVote();
    }
    else
    {
        `BiaDbg("MapVote already in effect");
    }
    
    `BiaExit();
}

function HandleMapVote()
{
    bMapVoteHandled = true;
    PrepVoteRound();
    PushState('Voting');
}

function MyKick( Controller C )
{
    local PlayerController PC;
    
    `BiaEnter();
    if( C != None )
    {
        PC = PlayerController(C);
        if( PC != None )
        {
            // its a player?
            if( PC.Pawn != None )
            {
                PC.Pawn.Suicide();
            }
            PC.ClientWasKicked();
            if( PC != None )
            {
                PC.Destroy();
            }
        }
        else
        {
            // itsa bot ?
            if( C.IsA('AIController') )
            {
                //Issue:4 - Can add too many bots - leaving this alone?
                nKickedBots = nKickedBots + 1;
                UTGame(WorldInfo.Game).DesiredPlayerCount =
                    max(max(UTGame(WorldInfo.Game).NumPlayers,2),
                        UTGame(WorldInfo.Game).NumPlayers + UTGame(WorldInfo.Game).NumBots -1 );
            }
            if( C.Pawn != None )
            {
                C.Pawn.Destroy();
            }
            C.Destroy();
        }
    }
    `BiaExit();
}

function string GetNextMap()
{
    local string sKind, sMap;

    bRestartGame = true;
    
    if( bStatsRecorded )
    {
        `BiaLog("stats recorded!");
    }
    else
    {
        `BiaError("stats were not recorded!");
    }
    
    sKind = "Winning";
    if( sNextMap == "" )
    {
        sNextMap = ChooseRandomMap();
        sKind = "Random";
    }
    
    sMap = MapNameFromURL( sNextMap );
    BroadCastMessage("Switching to " $ sKind $ " map: " $ sMap );
        
    return( sNextMap );
}


// ----------------------------------------------------------------------------
//
// Code dealing with setting up what is to be voted for...
//

function int BiaNewUId( optional biaVType vt = vtNone )
{
    if( nBiaUId < nBiaUId_minimum )
    {
        nBiaUId = nBiaUId_minimum;
    }
    nBiaUId += 1;
    return nBiaUId;
}

function biaAddVoteable( sVoteable V )
{
    biaVL.AddItem( V );
}

function biaAddGame( cfgGame g, int CfgIndex )
{
    local string title, gAcronym, gC, thisentry;
    local sVoteable V;
    local int idx;
    
    thisentry = "UTBiaVote.ini/Games[" $ CfgIndex $"] ";
    
    if( g.GameClass == "" )
    {
        `BiaError("Skipping " $thisentry$", no GameClass found!");
        return;
    }
    if( ! g.bEnabled )
    {
        `BiaWarn("Skipping " $thisentry$", bEnabled=false");
        return;
    }
    gAcronym = g.Game;
    if(gAcronym == "" || gAcronym == "*")
    {
        `BiaError("Skipping "$thisentry$", Acronym not set (Game=...)");
        return;
    }

    gC = g.GameClass;
    if( Instr(gC, ".") < 0 )
    {
        gC = GuessClassPackage( gC );
        if( Instr(gC, ".") < 0 )
        {
            `BiaError("Skipping " $thisentry$ " You must give the package in GameClass=" $ gC);
            return;
        }
        `BiaWarn( thisentry $ ", no package in GameClass=" $ g.GameClass $ ", using " $ gC );
    }
    
    if( gC ~= CurGameClass )
    {
        sDefGame = g.Game;
        CurVGame = g.Game;
        //TODO: flag this - if not flagged, it needs to be dealt with before loading maps...
    }
    
    title = g.Title;
    if(g.Title == "")
    {
        title = gC;
        idx = Instr(gC, ".");
        if( idx > 0 )
        {
            title = Mid(title, 1);
        }
    }
    
    V.Title = title;
    V.VType = vtGame;
    V.Value = gC;
    V.gMask.AddItem(gAcronym);
    V.cfgIndex = CfgIndex;
    
    aGames.AddItem( gAcronym );
    
    biaAddVoteable( V );
}

function biaAddAddBot( )
{
    local sVoteable V;
    
    V.VType = vtAddBot;
    v.Value = "Add 1 Bot";
    v.Title = v.Value;
    v.cfgIndex = 1;
    biaAddVoteable(V);
    
    v.Value = "Add 2 Bots";
    v.Title = v.Value;
    v.cfgIndex = 2;
    biaAddVoteable(V);
    
    v.Value = "Add 4 Bots";
    v.Title = v.Value;
    v.cfgIndex = 4;
    biaAddVoteable(V);
}

function biaAddMap( cfgMap m, int CfgIndex )
{
    local string title, sGames;
    local sVoteable V;
    local array<string> parts;
    local int i;

    if( m.Map == "")
    {
        `BiaError(".ini file, Maps=("$CfgIndex$") Map= was not given");
        return;
    }
    if( ! m.bEnabled )
    {
        `BiaWarn(".ini file, Maps=("$CfgIndex$") Map is disabled, skipping");
        return;
    }
    
    // TODO: check more stuff in the struct, ie: Games, maxPlayers, minPlayers, etc...

    // check to see if all games are enabled...
  
    title = m.Title;
    if(title == "")
    {
        title = m.Map;
        if( MapsRemovePrefix )
        {
            i = Instr(title, "-");
            if( i > 0 && i < Len(title) )
            {
                title = Mid(title, i+1);
            }
        }
    }

    // ok - add this map...
    
    V.Title = title;
    V.VType = vtMap;
    v.Value = m.Map;
    v.cfgIndex = CfgIndex;
    v.gMask.Length = 0;

/*    
    if( m.Games.Length == 0 )
    {
        V.gMask.AddItem( sDefGame );
    } else {
        foreach m.Games(game)
        {
            if( GameIsEnabled( game ) )
            {
                v.gMask.AddItem( game );
            }
        }
    }
*/

    
    sGames = m.Games;
    Repl(sGames," ", "");
    if(sGames == "")
    {
        v.gMask.AddItem(sDefGame);
    }
    else
    {
        ParseStringIntoArray(sGames, parts, ",", true);
        for(i = 0; i < parts.Length; i++)
        {
            if(parts[i] == "*")
            {
                AddAllGameTypes(v.gMask);
                break;
            }
            if( GameIsEnabled(parts[i]))
            {
                v.gMask[v.gMask.Length] = parts[i];
            }
        }
    }

    
    if( v.gMask.Length > 0 )
    {
        biaAddVoteable(V);
    }
}

function AddAllGameTypes( out array<string>gMask )
{
    local string s;
    gMask.Length = 0;
    foreach aGames(s)
    {
        gMask[gMask.Length] = s;
    }
    return;
}

function bool GameIsEnabled( string gAcronym )
{
    if( aGames.Find( gAcronym ) > -1 )
    {
        return true;
    }
    return false;
}

//TODO: build list of current mutators so that can be "un-voted for"
//TODO: add all the crap needed to vote for mutators, etc...
function FindCurrentMutators()
{
    local Mutator mut;
    for(mut = WorldInfo.Game.BaseMutator; mut != None; mut = mut.NextMutator )
    {
        `BiaDbg("Mutator: " $ mut.Class.GetPackageName() $ ":" $mut.Class );
    }
}

function SetupVote() {
    local int i; // , nv, id;
    
    bVoteReady = False;
    aGames.Length = 0;

    CurVGame = sDefGame;
    
    for(i = 0; i < Games.Length; i++)
    {
        biaAddGame( Games[i], i);
    }

    `BiaDbg("Setting up Current VGame, Default: " $ CurVGame);
    SetDefaultVGame();
    `BiaDbg("CurVGame is now: " $ CurVGame );
    
    for(i = 0; i < Maps.Length; i++)
    {
        biaAddMap( Maps[i], i );
    }

    if( EnableAddBotVoting )
    {
        biaAddAddBot();
    }
    

    //TODO: Add Mutator Voting...
    FindCurrentMutators();

    
    for(i=0; i < biaVL.Length; i++)
    {
        biaVL[i].Id = BiaNewUId();
    }
    
}

function SetDefaultVGame()
{
    local int i;
    local array<string> aGame;
    local string sFirst;
    local bool bDefGameValid;
    
    bDefGameValid = false;
    sFirst = "";
    aGame.Length = 0;
    
    `BiaDbg("CurGameClass: " $ CurGameClass );
    
    for(i = 0; i < biaVL.Length; i++ )
    {
        if( biaVL[i].vType == vtGame )
        {
            
            `BiaDbg("Checking Game Class: " $ biaVL[i].Value);
            
            `BiaAssert( biaVL[i].gMask.Length == 1 );
            
            if( sFirst == "" && biaVL[i].gMask.Length == 1)
            {
                sFirst = biaVL[i].gMask[0];
                `BiaDbg("First gametype listed: " $ sFirst);
            }
            if( biaVL[i].gMask.Length == 1 && biaVL[i].gMask[0] ~= DefaultGame )
            {
                bDefGameValid = true;
                `BiaDbg("defaultGame is valid, listed at index " $ i);
            }
            if( biaVL[i].gMask.Length == 1 && biaVL[i].Value ~= CurGameClass )
            {
                `BiaDbg("Found matching game class: " $ biaVL[i].Value $ ", Acronym: " $ biaVL[i].gMask[0]);
                aGame[aGame.Length] = biaVL[i].gMask[0];
            }
        }
    }
    `BiaDbg("Found " $ aGame.Length $ "Matching game(s)");
    if( aGame.Length == 1 )
    {
        CurVGame = aGame[0];
        `BiaDbg("One match found: " $ CurVGame);
        return;
    }
    else if (aGame.Length > 1)
    {
        for(i=0; i< aGame.Length; i++)
        {
            if( aGame[i] ~= sLastVGame )
            {
                CurVGame = aGame[i];
                `BiaLog("multiple game classes exist for " $ CurGameClass $ ", choice: " $ CurVGame);
                return;
            }
        }
        `BiaDbg("Did not find a match");
    }
    if( ! bDefGameValid )
    {
        `BiaError("DefaultGame in ini file does not match listed/enabled games");
        CurVGame = sFirst;
    }
    else
    {
        CurVGame = DefaultGame;
        `BiaWarn("Setting to DefaultGame stated in .ini: " $ CurVGame);
    }
}

function biaAddDL( biaVType vType, int Id, string Title, optional bool bBatchOp = false )
{
    local int idx;
    idx = biaDL.Find('Id', 0);
    if( idx < 0 )
    {
        idx = biaDL.Length;
        biaDL.Add(1);
    }
    biaDL[idx].Id = Id;
    biaDL[idx].vType = vType;
    biaDL[idx].Title = Title;
    
    // we've made a change to the dynamic-voteable list...
    if( ! bBatchOp )
    {
        `DynamicUIListModified();
    }
}

function biaDelDL( int Id, optional bool bBatchOp = false )
{
    local int idx;
    
    if (Id > 0)
    {
        //TODO: do I really need to do this?  There should be no dupes!
        while( true )
        {
            idx = biaDL.Find('Id', Id);
            if( idx > -1 )
            {
                biaDL[idx].Id = 0;
                biaDL[idx].vType = vtNone;
                biaDL[idx].Title = "";
                continue;
            }
            break;
        }
        // we've made a change to the dynamic-voteable list...
        if( ! bBatchOp )
        {
            `DynamicUIListModified();
        }
    }
}

function UIListAdd(biavType vt, string Title, int Id)
{
    local sUIList s;
    
    s.vType = vt;
    s.Title = Title;
    s.Id = Id;
    
    UILists.AddItem(s);
}

function SetupLists( optional bool force=false )
{
    local sVoteable v;
    local bool bUpToDate;
    
//    local string s,ss;
`if(`isdefined(DEBUG))
    local sUIList DbgU;
    local int DbgIdx;
`endif

    `BiaEnter();
    
    bUpToDate = false;
    if( sLastListGameCat != "" )
    {
        if( sLastListGameCat ~= CurVGame )
        {
            if( force == false )
            {
                bUpToDate = true;
            }
        }
    }
    
    if( bUpToDate )
    {
        `BiaDbg("Lists seem up to date...");
    }

    sLastListGameCat = CurVGame;
    
    UILists.Length = 0;

    UIListAdd(vtCat, "Maps",  5000);
    UIListAdd(vtCat, "Games", 5001);
    if( EnableKickVoting )
    {
        UIListAdd(vtCat, "Kick",  5002);
    }
    if( EnableAddBotVoting )
    {
        UIListAdd(vtCat, "AddBots", 5003);
    }

    `BiaLog("CurVGame is: " $ CurVGame);
    
    foreach biaVL( v )
    {
        if(v.vType == vtGame)
        {
            UIListAdd(v.vType, v.Title, v.Id);
        }
        else if ( v.vType == vtMap )
        {
            if( v.gMask.Find( CurVGame ) > -1 ) 
            {
                UIListAdd(v.vType, v.Title, v.Id);
            }
        }
        else if( v.vType == vtAddBot )
        {
            UIListAdd(v.vType, v.Title, v.Id);
        }
    }

`if(`isdefined(DEBUG))
    DbgIdx = 0;
    foreach UILists( DbgU )
    {
        `BiaDbg("UIList("$DbgIdx$") type(" $ DbgU.vType $ "), Title(" $ DbgU.Title $")");
        DbgIdx += 1;
    }
`endif

    `StaticUIListModified(); // important to flag that the data has changed...
    
    `BiaExit();
}


// ----------------------------------------------------------------------------
//
// Client (BiaVoteClient) interface
//

function CloseClients()
{
    local sPlayer P;
    local string sMessage;
    local int idx;
    
    if( sNextMap != "" )
    {
        sMessage = "Loading " $ sNextMap;
        idx = Instr(sMessage, "?");
        if( idx > 0 )
        {
            sMessage = Left(sMessage, idx);
        }
    }
    else
    {
        sMessage = "Switching to a random Map";
    }
    foreach aPlayers(P)
    {
        if(P.BVC != None)
        {
            P.BVC.DoRestart(sMessage);
        }
    }
}

function UpdateClientVotes()
{
    local int i;

    `BiaDbg("nVoteListVer:" $nVoteListVer);    

    for(i = 0; i < aPlayers.Length; i++)
    {
        if( IsCitizen(i) && aPlayers[i].BVC != None )
        {
            aPlayers[i].BVC.SyncVotes();
//                P.BVC.DoSync();
        }
    }
}

// SubmitVote
function Player_SubmitVote(int PlayerIndex, int Id )
{
    local sVoteable v;
    local int idx;

    `BiaEnter();
    
    `BiaDbg("received vote from: " $ aPlayers[PlayerIndex].PlayerName );

    idx = biaVL.Find('Id', Id);
    if( idx > -1 )
    {
        `BiaDbg("received vote for: " $ Id $ "=" $ biaVL[idx].Title );
        v.vType = biaVL[idx].vType;
        v.Id = biaVL[idx].Id;
        v.Title = biaVL[idx].Title;
        UpdateVotes( v, PlayerIndex );
        return;
    }
    
    // kick vote?
    idx = aPlayers.Find('Id', Id);
    if( idx > -1 )
    {
        if( aPlayers[idx].PC != None )
        {
            v.vType = vtKick;
            v.Id = Id;
            v.Title = "Kick " $ aPlayers[idx].PlayerName;
            UpdateVotes( v, PlayerIndex );
            return;
        }
    }
    
    PlayerAckVote(PlayerIndex, "Failed to register vote!");
    `BiaExit();    
}

private function PlayerAckVote(int PlayerIndex, string Msg )
{
    if( aPlayers[PlayerIndex].PC != None && aPlayers[PlayerIndex].BVC != None )
    {
        aPlayers[PlayerIndex].BVC.AckVote( Msg );
    }
}

reliable server function SubmitVote( int Id, PlayerController PC, BiaVoteClient BVC )
{
    
    local int idx;

    `BiaEnter();
    
    for(idx = 0; idx < aPlayers.Length; idx++)
    {
        if(aPlayers[idx].PC == PC && IsCitizen(idx))
        {
            Player_SubmitVote(idx, Id);
        }
    }
    
    `BiaExit();    
}

// to support the console command "mutate biavote"
function PlayerOpenUI( PlayerController PC )
{
    local int idx;
    if( PC != None )
    {
        for(idx = 0; idx < aPlayers.Length; idx++)
        {
            if( aPlayers[idx].PC == PC && aPlayers[idx].BVC != None )
            {
                aPlayers[idx].BVC.DoMapVote();
                return;
            }
        }
    }
}
// ---------------------------------------------------------------------------
//
// Misc utility functions...
//

function string CurVGameToGameClass()
{
    local sVoteable vl;
    
    foreach biaVL(vl)
    {
        if( vl.vType == vtGame )
        {
            if( vl.gMask.Length > 0 && vl.gMask[0] ~= CurVGame )
            {
                return( vl.Value );
            }
        }
    }
    `BiaError("Found no matching game class for : " $ CurVGame);
    return( CurGameClass );
}

function string CurVGameToOpts( )
{
    local sVoteable vl;
    local int cfgIndex;
    
    cfgIndex = -1;
    foreach biaVL(vl)
    {
        if( vl.vType == vtGame )
        {
            if( vl.gMask.Length > 0 && vl.Gmask[0] ~= CurVGame )
            {
                cfgIndex = vl.cfgIndex;
                break;
            }
        }
    }
    if( cfgIndex > -1 && cfgIndex < Games.Length )
    {
        return( Games[cfgIndex].Options );
    }
    return("");
}

function BroadCastMessage( string msg )
{
    WorldInfo.Game.Broadcast(Self, msg );
}



// If no string is given, then the URL for the current game is used...
function string MapNameFromURL( optional string TheUrl=WorldInfo.GetLocalURL() )
{
    local int i;
    local string TheMap;
    
    TheMap = "";
    if( TheURL != "" )
    {
        TheMap = TheURL;
        i = Instr(TheURL, "?");
        if( i > -1 )
        {
            TheMap = Left(TheURL, i);
        }
    }
    return TheMap;    
}

function string ChooseRandomMap()
{
    local sVoteable vl;
    local array<string> Choices;
    local string CurMap;
    
    CurMap = MapNameFromURL();
    
    foreach biaVL(vl)
    {
        if( vl.vType == vtMap && vl.gMask.Find( sDefGame ) > -1 )
        {
            if( vl.Value != CurMap )
            {
                Choices.AddItem( vl.Value );
            }
        }
        
    }
    if( Choices.Length == 1 )
    {
        return Choices[0];
    }
    if( Choices.Length < 1 )
    {
        return sFailSafeURL;
    }
    
    return Choices[Rand(Choices.Length)];
}

// Only "Citizens" have the right to vote, dammit!!!
function bool IsCitizen( int Idx )
{
    local PlayerReplicationInfo PRI;
    
`if(`isdefined(UNIT_TEST))
    if( aPlayers[Idx].bUnitTest )
    {
        return(true);
    }
`endif
    
    if( Idx > -1 && Idx < aPlayers.Length )
    {
        if( aPlayers[Idx].bIsBot || aPlayers[Idx].PC == None || aPlayers[Idx].BVC == None )
        {
            return false;
        }
        if( aPlayers[Idx].PC.IsA('PlayerController') )
        {
            PRI = UTPlayerController(aPlayers[idx].PC).PlayerReplicationInfo;
            if( PRI != None )
            {
                if( ! PRI.bOnlySpectator && ! PRI.bIsSpectator )
                {
                    `BiaDbg(aPlayers[Idx].PlayerName $ " is a citizen");
                    return true;
                }
            }
        }
    }
    return false;
}

function int CountCitizens()
{
    local int folk, idx;

    folk = 0;
    for(idx = 0; idx < aPlayers.Length; idx++)
    {
        folk += IsCitizen(idx) ? 1 : 0;
    }
    return( folk );
}

// ---------------------------------------------------------------------------

// SetCurGameClass()
//
// Finds the fully qualified (package.game) name of the current/running game.
//
function SetCurGameClass()
{
    CurGameClass = WorldInfo.Game.default.Class.GetPackageName() $"."$ WorldInfo.Game.Class ;
}




// ----------------------------------------------------------------------------
//
// Unit Test -- primarly for validating I didn't screw up the vote counting
// logic -- kinda hard to test the vote evaluation code with only 1 player...
//

`if(`isdefined(UNIT_TEST))
function DoUnitTests()
{
    
    `BiaDbg("Running Unit Tests");
    bvUT = Spawn( Class'BiaVote.BiaVoteUnitTest', self );
    bvUT.StartTests(self);
}
`endif


// ----------------------------------------------------------------------------
//
// Static members - used by other classes, etc...

static function string GuessClassPackage( string klass )
{
    local string sk;
    if( Instr(klass, ".") < 0 )
    {
        sk = Locs(klass);
        switch(sk)
        {
            case "utdeathmatch":
            case "utteamgame":
            case "utduelgame":
                return "UTGame." $ klass;
            case "utctfgame_content":
            case "utvehiclectfgame_content":
            case "utonslaughtgame_content":
                return "UTGameContent." $ klass;
        }
    }
    return klass;
}

static function string vTypeToListName( biaVType vt )
{
    switch( vt )
    {
        case vtCat:
            return "BiaVoteCat";
        case vtVote:
            return "BiaVotes";
        case vtMap:
            return "BiaVoteCatMaps";
        case vtGame:
            return "BiaVoteCatGames";
        case vtKick:
            return "BiaVoteCatKick";
        case vtAddBot:
            return "BiaVoteCatAddBots";
    }
    return `bv_vTypeUnlisted ;
}

static function BiaVote GetInstance()
{
    local BiaVote Def;
    Def = BiaVote(FindObject("BiaVote.Default__BiaVote", class'BiaVote.BiaVote'));
    if( Def != None )
    {
        return Def.MyInstance;
    }
    return None;
}

static function NotifyLogin( Controller C )
{
    local BiaVote Inst;
    Inst = GetInstance();
    if(Inst == None )
    {
        `BiaError( s_err_no_instance );
        return;
    }
    Inst.ServerLogin(C);
}

static function ExecPlayerOpenUI( PlayerController PC )
{
    local BiaVote Inst;
    Inst = GetInstance();
    if( Inst == None )
    {
        `BiaError( s_err_no_instance );
        return;
    }
    Inst.PlayerOpenUI( PC );
}


static function NotifyLogout( Controller C )
{
    local BiaVote Inst;
    Inst = GetInstance();
    if( Inst == None )
    {
        `BiaError( s_err_no_instance );
        return;
    }
    Inst.PlayerLogout( C );
}

static function bool IsRestarting()
{
    local BiaVote Inst;
    Inst = GetInstance();
    if( Inst == None )
    {
        return(true);
    }
    return Inst.bRestartGame;
}


static function UTVoteCollector GetMyVoteCollector()
{
    local BiaVote Inst;
    Inst = GetInstance();
    if( Inst == None )
    {
        `BiaError( s_err_no_instance );
        return None;
    }
    return Inst.SpawnFauxVoteCollector();
}
static function GameDoMapVote()
{
    local BiaVote Inst;
    Inst = GetInstance();
    if( Inst == None )
    {
        `BiaError( s_err_no_instance );
        return;
    }
    Inst.PrepForMapVote();
}    

static function string GetWinningMap()
{
    local BiaVote Inst;
    Inst = GetInstance();
    if( Inst == None )
    {
        `BiaError( s_err_no_instance );
        return "";
    }
    return Inst.GetNextMap();
}

static function bool CanShowWelcomeScreen( PlayerController PC )
{
    local BiaVote Inst;
    Inst = GetInstance();
    
    `BiaDbg("should we welcome " $ PC.PlayerReplicationInfo.PlayerName $ "?");
    if( Inst == None )
    {
        `BiaError( s_err_no_instance );
        return false; // seems reasonable.
    }
    if( Inst.EnableWelcome < 0 )
    {
        `BiaDbg("never show");
        return false;
    }
    if( Inst.EnableWelcome > 0 )
    {
        `BiaDbg("always show");
        return true;
    }
    if( Inst.MyStats != None )
    {
        `BiaDbg("show once per login to server, but not after travel -- checking stats...");
        return( Inst.MyStats.IsTraveling( PC ) ? false : true );
    }
    `BiaDbg("unable to find Stats object.");
    return true;
}

auto state Spawned
{
    function ScanForNewBots() {}
    function BeginState( name PrevState )
    {
        SpawnTime = WorldInfo.TimeSeconds;
        SetTimer(0.05, false);
    }
    function Timer()
    {
        if( WorldInfo.Game != None )
        {
            if( WorldInfo.Game.GameReplicationInfo != None )
            {
                ClearTimer();
                GotoState('');
                BiaVote_ServerInit( WorldInfo.Game.GameReplicationInfo );
                return;
            }
        }
        if( WorldInfo.TimeSeconds - SpawnTime > 10 )
        {
            ClearTimer();
            GotoState('');
            `BiaError("soemthing is wrong, not finding GRI! - giving up, there will be no vote this game");
        }
        // So, try again... Note: don't use 'true' - can call too fast...
        SetTimer(0.05,false);
    }
}

static function bool SpawnInstance( GameInfo GI )
{
    local BiaVote Me;
    Me = GetInstance();
    if( Me == None )
    {
        Me = BiaVote(FindObject("BiaVote.Default__BiaVote", class'BiaVote.BiaVote'));
        Me.MyInstance = GI.Spawn(Class'BiaVote.BiaVote', GI );
        
        Me = GetInstance();
        if( Me != None )
        {
            `BiaDbg("Successfully created an instance of BiaVote");
            return true;
        }
        else
        {
            `BiaError( s_err_no_instance );
        }
        
    }
    else
    {
        `BiaWarn("Instance of BiaVote exists!");
    }
    return false;
}


static function ShowConfig()
{
    local int i;
    for(i=0; i < default.Games.Length; i++ )
    {
        LogInternal("Game: " $ default.Games[i].Game );
    }
}


`if(`isdefined(DEBUG))
//
// just to make sure that GC will happen - was not a problem when this was
// all integrated into a custom GameType, but as a Mutator, I have far less
// control over things :-(
//
static function ReportWorldObjects(WorldInfo WI)
{
    local PlayerController PC;
    local Actor Ac;
    local int tot, ti, tp, tvc;
    
    foreach WI.AllActors(class'Actor', Ac)
    {
        if( Ac.IsA('Info') )
        {
            `BiaDbg("Info Object            : " $ Ac);
            ti++;
            if( Ac.IsA('UTVoteCollector') )
            {
                tvc++;
            }
        }
        if( Ac.IsA('Pawn'))
        {
            tp++;
        }
        tot++;
    }
    `BiaDbg(    "Total Info ........... : " $ ti);
    `BiaDbg(    "Total VoteCollector... : " $ tvc);
    `BiaDbg(    "Total Pawn ........... : " $ tp);
    `BiaDbg(    "Total Actors ......... : " $ tot);
    
    tot=0;
    foreach WI.AllControllers(class'PlayerController', PC)
    {
        `BiaDbg("PlayerController Object: " $ PC );
        tot++;
    }
    `BiaDbg(    "Total PlayerController : " $ tot);
}
`endif



defaultproperties
{
    
    // mutator interface
    MyInstance=None
    bMutatorInterface=true

    bVoteReady=False
    sNextMap=""
    sDefGame="DM"
    sFailSafeURL="DM-HeatRay?Mutator=BiaVote.BiaVoteMutator"
    sLastListGameCat=""
    
    
    bMapVote=false
    bMapVoteHandled=false
    
    // NOTE: this *MUST* be greater than 16 seconds! - see: UTPlayerController.uc 
    minVoteTimeLimit=20
    
    maxVoteTimeLimit=120
    nVoteTimeLimit=60
    nKickedBots=0
    nExtraBots=0

    aVoteRules(0)=(vType=vtKick,nMultiWin=2)
    aVoteRules(1)=(vType=vtAddBot,nMultiWin=1)
//    aVoteRules(1)=(vType=vtMut,nMultiWin=3,bOnlyGameSwitch=true)

}

